<?php

// Namespace

namespace Loop;


// Set paths

if (defined('PATH_ROOT') == false)
{
	$pathToRoot = realpath(dirname($_SERVER['SCRIPT_FILENAME']));

	if (empty($pathToRoot) === true)
	{
		define('PATH_ROOT', './');
	}
	else
	{
		define('PATH_ROOT', $pathToRoot . '/');
	}
	
}

define('PATH_APPLICATION', PATH_ROOT . 'Application/');
define('PATH_EXTENSION', PATH_ROOT . 'Extension/');
define('PATH_TEMPORARY', PATH_ROOT . 'Temporary/');

define('PATH_LOOP', PATH_EXTENSION . 'Loop/');


/**
 * Callback called in case of error
 */

function onError($errorNumber, $errorString, $errorFile = null, $errorLine = null, $errorContext = null)
{
	// Log the error in the console

	error_log($errorString . ' (#' . $errorNumber . ', in ' . $errorFile . ', line ' . $errorLine . ')');


	// Let the exception manager handle the error

	app()->getExceptionManager()->onError($errorNumber, $errorString, $errorFile, $errorLine, $errorContext);


	return true;
}


/**
 * Callback called in case of exception
 */

function onException($exception)
{
	// Log the exception in the console

	error_log($exception->getCode() . ' (in ' . $exception->getFile() . ', line ' . $exception->getLine() . ')');

	if (method_exists($exception, 'getContext') === true)
	{
		error_log(print_r($exception->getContext(), true));
	}


	// Let the exception manager handle the exception

	app()->getExceptionManager()->onException($exception);


	return true;
}


// Use Loop to catch exceptions / errors

set_error_handler('Loop\onError');
set_exception_handler('Loop\onException');


// Dependencies (order is important)

require_once(PATH_LOOP . 'Class/Proxy.php');

require_once(PATH_LOOP . 'Constant/Exception.php');
require_once(PATH_LOOP . 'Constant/Language.php');
require_once(PATH_LOOP . 'Constant/Operator.php');

require_once(PATH_LOOP . 'Class/CObject.php');


/**
 * CApplication is a singleton that acts as main point of entry
 * It takes care of initalizing what's required to eventually render a view (either a HTML page, an API, a JS/CSS, etc...).
 * This class contains mainly managers (for strings, preferences, etc) and provides access to them.
 *
 * @package		Loop
 * @category	Core
 */

class CApplication
{
	// Properties
	
	private $databaseDrivers = null;
	private $concepts = null;
	private $user = null;
	
	private static $instance = null;
	
	
	/**
	 * The object constructor, private so that CApplication remains a singleton.
	 */
	
	private function __construct()
	{
		$this->databaseDrivers = array();
		$this->concepts = array();
	}
	
	
	/**
	 *
	 */
	
	public function __call($functionName, $functionArguments = null)
	{
		if (preg_match('/^get(([A-Z][a-z]*)+)([A-Z][a-z]*)$/', $functionName, $matches) === 1)
		{
			return $this->getConcept($matches[3], $matches[1]);
		}
		else
		{
			e
			(
				EXCEPTION_FUNCTION_NOT_FOUND,
				array
				(
					'functionName' => $functionName
				)
			);
		}
	}

	
	/**
	 * Accessor to get the database driver
	 *
	 * @return	IDatabaseDriver	The database driver
	 */
	
	public function getDatabaseDriver($extensionId)
	{
		// Create the driver if needed

		if (isset($this->databaseDrivers[$extensionId]) === false)
		{
			$this->databaseDrivers[$extensionId] = $this->getDatabaseDriverFactory()->createDatabaseDriver($extensionId);
		}

		
		return $this->databaseDrivers[$extensionId];
	}
	
	
	/**
	 * Creates (if necessary) and returns a unique instance of CApplication
	 *
	 * @return	CApplication	The unique instance of CApplication
	 */
	
	public static function getInstance()
	{
		// Create the application if needed
		
		if (is_null(CApplication::$instance) === true)
		{
			\Loop\CApplication::$instance = new \Loop\CApplication();
		}
		
		
		return \Loop\CApplication::$instance;
	}
	
	
	/**
	 *
	 */
	
	public function getConcept($concept, $conceptName)
	{
		// Create the manager if needed
		
		if (isset($this->concepts[$concept]) === false)
		{
			$this->concepts[$concept] = array();
		}


		//

		if (isset($this->concepts[$concept][$conceptName]) === false)
		{
			//

			$extensionId = 'Loop';

			if
			(
				($concept != 'Manager') ||
				($conceptName != 'Preference')
			)
			{
				$extensionId = pref('[Loop]/' . lcfirst($concept) . '/' . lcfirst($conceptName) . '/extensionId');
			}

			if (empty($extensionId) === true)
			{
				$extensionId = 'Loop';
			}


			//

			$namespace = '\\' . str_replace('-', '\\', $extensionId) . '\\';
			$pathToExtension = PATH_ROOT . 'Extension/' . $extensionId . '/';

			if
			(
				($concept != 'Manager') ||
				($conceptName != 'Preference')
			)
			{
				if ($extensionId === pref('[Loop]/application/code'))
				{
					$pathToExtension = PATH_ROOT . 'Application/';
				}
			}

	
			//

			$classNameWithoutNamespace = 'C' . ucfirst($conceptName) . ucfirst($concept);
			$className = $namespace . $classNameWithoutNamespace;

			$pathToConcept = $pathToExtension . 'Class/' . $classNameWithoutNamespace . '.php';


			//

			if (file_exists($pathToConcept) === false)
			{
				e
				(
					'EXCEPTION_APPLICATION_CONCEPT_NOT_FOUND',
					array
					(
						'concept' => $concept,
						'conceptName' => $conceptName,
						'pathToConcept' => $pathToConcept
					)
				);
			}
			
			
			// Include the PHP file
			
			require_once($pathToConcept);
			

			// Create the concept
			
			$this->concepts[$concept][$conceptName] = new $className();
		}
		
		
		return $this->concepts[$concept][$conceptName];
	}
	
	
	/**
	 * Gets the current user
	 *
	 * @return	CUser	The current user
	 */
	
	public function getUser()
	{
		// Do we have a user already?
		
		if (is_null($this->user) === true)
		{
			//
			
			$className = 'CUser';
			$extensionId = EXTENSION_APPLICATION;
			$pathToFile = PATH_APPLICATION . 'Model/' . $className . '.php';


			// Include the PHP file
			
			require_once($pathToFile);


			// Is there a namespace ?

			$namespace = app()->getPathHelper()->convertExtensionIdToNamespace($extensionId);

			if (is_null($namespace) === false)
			{
				$className = $namespace . $className;
			}


			// Create the user
			
			$this->user = new $className();
		}
		
		
		return $this->user;
	}
	
	
	/**
	 * Initializes the application
	 */
	
	public function initialize()
	{
		// In CLI mode, there cannot be memory limitations

		if ($this->isRunningCli() === true)
		{
			ini_set('memory_limit', '-1');

			pref
			(
				'[Loop]/application/universe',
				fv('universe', false),
				true
			);
		}


		// Start initializing managers

		$this->getExtensionManager()->initialize();
		$this->getConstantManager()->initialize();
		$this->getPreferenceManager()->initialize();

		
		// Define the constants PATH_MY_APPLICATION and EXTENSION_MY_APPLICATION
		
		$constantName = strtoupper(str_replace('-', '_', pref('[Loop]/application/code')));
		
		cons('PATH_' . $constantName, PATH_APPLICATION);
		cons('EXTENSION_' . $constantName, pref('[Loop]/application/code'));
		cons('EXTENSION_APPLICATION', pref('[Loop]/application/code'));


		// Initialize more managers

		$this->getExceptionManager()->initialize();

		if ($this->isRunningCli() == false)
		{
			$this->getSessionManager()->initialize();
		}
		

		// Initialize universe in case of callback url with universe in session
		// If in CLI mode, grab the universe

		$universe = fv('universe', false);

		if (empty($universe) === false)
		{
			pref
			(
				'[Loop]/application/universe',
				fv('universe', false),
				true
			);
		}


		// Finish initialization of managers

		$this->getViewManager()->initialize();
		$this->getStringManager()->initialize();

		if ($this->isRunningCli() === false)
		{
			$this->getSecurityManager()->initialize();			
		}
		

		// If the language on the URL doesn't match the language in preferences, redirect to the right URL
		
		if
		(
			($this->isRunningCli() == false) &&
			($this->getViewManager()->getRootView()->type != VIEW_TYPE_ACTION) &&
			($this->getViewManager()->getRootView()->type != VIEW_TYPE_API) &&
			(pref('[Loop]/localization/languageIsoCode') != v(pref('[Loop]/authentication/source') . '::previousLanguageIsoCode')) &&
			(strpos(v('SERVER::REQUEST_URI'), v(pref('[Loop]/authentication/source') . '::previousLanguageIsoCode')) === 1)
		)
		{
			$uri = str_replace
			(
				'/' . v(pref('[Loop]/authentication/source') . '::previousLanguageIsoCode') . '/',
				pref('[Loop]/localization/languageIsoCode') . '/',
				v('SERVER::REQUEST_URI')
			);
			
			if ($uri[0] == '/')
			{
				$uri = substr($uri, 1);
			}
			
			v(pref('[Loop]/authentication/source') . '::previousLanguageIsoCode', pref('[Loop]/localization/languageIsoCode'));
			
			redirect(root(false) . $uri);
		}

		
		// Is the application under maintenance?
		
		$pathToFile = path(array(PATH_TEMPORARY));
		$pathToFile .= 'Maintenance.txt';
		
		if (file_exists($pathToFile) === true)
		{
			// Do we have the key to bypass it?
			
			$maintenanceKey = file_get_contents($pathToFile);
			
			if
			(
				(empty($maintenanceKey) === true) ||
				($maintenanceKey != fv('maintenanceKey', false))
			)
			{
				e(EXCEPTION_APPLICATION_UNDER_MAINTENANCE);
			}
		}
	}
	
	
	/**
	 * Determines whether the application is running on a given environment (i.e. on the production server)
	 *
	 * @param	string	$environmentUri	The URI of the environment
	 *
	 * @return	bool					True if running on the given environment, false otherwise
	 */
	
	public function isEnvironmentUri($environmentUri)
	{
		if (is_array($environmentUri) === true)
		{
			foreach ($environmentUri as $uri)
			{
				if ($this->isEnvironmentUri($uri) === true)
				{
					return true;
				}
			}
		}
		else
		{
			$environmentUri = str_replace(array('.', '-', '/'), array('\.', '\-', '\/'), $environmentUri);
			$isEnvironmentUri = (preg_match('/' . $environmentUri . '/', v('SERVER::HTTP_HOST') . v('SERVER::REQUEST_URI')) === 1);
			
			return $isEnvironmentUri;
		}
	}
	
	
	/**
	 * Checks whether the application is running in CLI
	 *
	 * @return	bool	True if running in CLI, false otherwise
	 */
	
	public function isRunningCli()
	{
		$httpHost = v('SERVER::HTTP_HOST');
		
		return (empty($httpHost) === true);
	}
	
	
	/**
	 * Quits the application
	 */
	
	private function quit()
	{
		//
		
		pstep(PROFILING_STEP_QUIT);


		//

		$this->getProfilingManager()->save();


		// Close connections to the database

		foreach ($this->databaseDrivers as $databaseDriver) 
		{
			$databaseDriver->disconnect();
		}


		// If in CLI mode, exit with 0

		if ($this->isRunningCli() === true)
		{
			exit(0);
		}
	}
	
	
	/**
	 * Resets the application
	 */
	
	public function reset()
	{
		// Delete the whole Temporary folder
		
		app()->getFileHelper()->deleteDirectory(PATH_TEMPORARY);
	}
	
	
	/**
	 * Runs the application, hence takes care of all initializations and rendering of the current view
	 */
	
	public function run()
	{
		// Measure time and memory for profiling
		
		v('CApplication::maxMemoryAtStart', memory_get_peak_usage());
		v('CApplication::timeOfStart', microtime(true));
	
		
		// Initialize the application
		
		$this->initialize();
		
		
		// Render the view from the URI
		
		pstep(PROFILING_STEP_RENDER);
		$this->getViewManager()->renderRootView();
		
		
		// Quit the application
		
		$this->quit();
	}
}

?>
